<script>
/**
 * @Author: 焦质晔
 * @Date: 2019-06-20 10:00:00
 * @Last Modified by: hexingjia
 * @Last Modified time: 2020-10-27 19:25:17
 **/
import _ from 'lodash';
import moment from 'moment';
import { sleep } from '@/utils';
import pinyin, { STYLE_FIRST_LETTER } from '@/components/Pinyin/index';
import Cascader from './Cascader.vue';
import BreakSpace from '@/components/BreakSpace/BreakSpace.vue';
import UploadFile from '@/components/UploadFile/UploadFile.vue';
import UploadCropper from '@/components/UploadCropper/UploadCropper.vue';
import Tinymce from '@/components/Tinymce/Tinymce.vue';

export default {
  name: 'FormPanel',
  props: {
    list: {
      type: Array,
      required: true
    },
    formType: {
      type: String,
      default: 'add'
    },
    cols: {
      type: Number,
      default: 3
    },
    labelWidth: {
      type: [Number, String],
      default: 80
    },
    labelPosition: {
      type: String,
      default: 'left'
    },
    disabled: {
      type: Boolean,
      default: false
    },
    isSubmitBtn: {
      type: Boolean,
      default: true
    },
    scrollContainer: {
      default: null
    }
  },
  data() {
    this.treeProps = { children: 'children', label: 'text' };
    this.prevForm = null;
    this.arrayTypes = ['RANGE_DATE', 'RANGE_TIME', 'RANGE_TIME_SELECT', 'RANGE_INPUT', 'RANGE_INPUT_NUMBER', 'MULTIPLE_SELECT', 'MULTIPLE_CHECKBOX', 'UPLOAD_IMG', 'UPLOAD_FILE'];
    return {
      form: {},
      visible: {},
      loading: false,
      isupload: true
    };
  },
  computed: {
    fieldNames() {
      return this.list
        .filter(x => !x.hidden)
        .map(x => x.fieldName)
        .filter(x => !!x);
    },
    formItemList() {
      const res = [];
      this.list
        .filter(x => !x.hidden && x.fieldName)
        .forEach(x => {
          if (x.type === 'BREAK_SPACE') return;
          if (_.isObject(x.labelOptions) && x.labelOptions.fieldName) {
            res.push(x.labelOptions);
          }
          res.push(x);
        });
      return res;
    },
    rules() {
      const target = {};
      this.list
        .filter(x => !x.hidden)
        .forEach(x => {
          if (!(x.fieldName && x.rules)) return;
          target[x.fieldName] = x.rules;
        });
      return target;
    }
  },
  watch: {
    formItemList: {
      handler(nextProps, prevProps) {
        if (nextProps.length !== prevProps.length) {
          this.initialHandle();
        }
        this.debounce(this.resetFormData, 10)(nextProps);
      },
      deep: true
    },
    form: {
      handler(nextProps) {
        const res = this.difference(nextProps, this.prevForm);
        if (!Object.keys(res).length) return;
        for (let key in res) {
          let target = this.formItemList.find(x => x.fieldName === key);
          if (!target) continue;
          // 同步 initialValue 的值
          target.initialValue = res[key];
        }
        this.prevForm = { ...nextProps };
      },
      deep: true
    },
    fieldNames(nextProps, prevProps) {
      if (!_.isEqual(nextProps, prevProps)) {
        this.initialHandle();
      }
      this.$nextTick(() => this.doClearValidate(this.$refs.form));
    }
  },
  created() {
    this.initialHandle();
  },
  methods: {
    initialHandle() {
      this.form = this.createFormData();
      this.prevForm = { ...this.form };
    },
    getInitialValue(item) {
      let { initialValue, type = '', fieldName, numberFormat, secretType, readonly } = item;
      if (this.formType === 'show') {
        item.disabled = true;
      }
      if (this.arrayTypes.includes(type)) {
        initialValue = initialValue || [];
      }
      if (type === 'INPUT' && numberFormat && (readonly || item.disabled)) {
        initialValue = this.formatNumber(initialValue);
      }
      if (type === 'INPUT' && secretType && (readonly || item.disabled)) {
        initialValue = this.secretFormat(initialValue, secretType);
      }
      // 树选择器
      if (type === 'INPUT_TREE' && _.isUndefined(this[`${fieldName}TreeFilterTexts`])) {
        this[`${fieldName}TreeFilterTexts`] = '';
      }
      // 级联选择器
      if (type === 'INPUT_CASCADER' && _.isUndefined(this[`${fieldName}CascaderTexts`])) {
        this[`${fieldName}CascaderTexts`] = '';
      }
      return initialValue;
    },
    createFormData() {
      const target = {};
      this.formItemList.forEach(x => {
        const val = this.getInitialValue(x);
        // 设置 initialValue 为响应式数据
        this.$set(x, 'initialValue', val);
        // 初始值
        target[x.fieldName] = val;
      });
      return target;
    },
    resetFormData(list) {
      list.forEach(x => {
        if (_.isEqual(x.initialValue, this.form[x.fieldName])) return;
        this.form[x.fieldName] = this.getInitialValue(x);
        // 对组件外 js 动态赋值的表单元素进行校验
        this.doFormItemValidate(x.fieldName);
      });
    },
    createFormItemLabel(option) {
      const { form } = this;
      const { label, type = 'SELECT', fieldName, itemList, options = {}, style = {}, disabled, change = () => {} } = option;
      const { trueValue = '1', falseValue = '0' } = options;
      return (
        <div class="label-wrap" style={{ ...style }}>
          {type === 'SELECT' && (
            <el-select v-model={form[fieldName]} placeholder={''} disabled={disabled} onChange={change}>
              {itemList.map(x => (
                <el-option key={x.value} label={x.text} value={x.value} />
              ))}
            </el-select>
          )}
          {type === 'CHECKBOX' && (
            <span style="vertical-align: middle">
              <span class="desc-text" style={{ paddingRight: '10px' }}>
                {label}
              </span>
              <el-checkbox v-model={form[fieldName]} trueLabel={trueValue} falseLabel={falseValue} disabled={disabled} onChange={change} />
            </span>
          )}
        </div>
      );
    },
    createFormItemDesc(option) {
      if (!option) return null;
      const { isTooltip, style = {}, content = '描述信息...' } = option;
      if (isTooltip) {
        return (
          <el-tooltip effect="dark" content={content} placement="right">
            <i class="desc-icon el-icon-info"></i>
          </el-tooltip>
        );
      }
      return (
        <span class="desc-text" style={{ paddingLeft: '10px', ...style }}>
          {content}
        </span>
      );
    },
    aaa(x) {
      <el-tooltip class="item" effect="dark" content="Top Center 提示文字" placement="top"></el-tooltip>;
      console.log(x, '=>lxx');
    },
    RENDER_FORM_ITEM(option) {
      const { label, fieldName, labelWidth, labelOptions, style = {}, render = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <div class="desc-text" style={{ ...style }}>
            {render()}
          </div>
        </el-form-item>
      );
    },
    INPUT(option) {
      const { form } = this;
      const {
        label,
        fieldName,
        labelWidth,
        labelOptions,
        descOptions,
        showPassword,
        numberFormat,
        style = {},
        placeholder = '请输入...',
        unitRender,
        minlength = 0,
        maxlength = 50,
        pattern,
        autofocus,
        readonly,
        disabled,
        showWordLimit = !1,
        change = () => {},
        onInput = () => {},
        onFocus = () => {},
        onEnter = () => {}
      } = option;
      const prevValue = form[fieldName];
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-input
            value={prevValue}
            onInput={val => {
              if (_.isRegExp(pattern)) {
                // 是否为删除动作
                const isRemoveHandle = val.length < (prevValue && prevValue.length);
                // 单元格正则校验
                if (!isRemoveHandle && !pattern.test(val)) return;
              }
              form[fieldName] = numberFormat ? this.formatNumber(val) : val;
              onInput(val);
            }}
            minlength={minlength}
            maxlength={maxlength}
            show-word-limit={showWordLimit}
            placeholder={!disabled ? placeholder : ''}
            autofocus={autofocus}
            readonly={readonly}
            disabled={disabled}
            showPassword={showPassword}
            style={{ ...style }}
            clearable
            onChange={val => {
              form[fieldName] = val.trim();
              change(form[fieldName]);
            }}
            onFocus={onFocus}
            nativeOnKeydown={e => {
              if (e.keyCode !== 13) return;
              onEnter(e.target.value);
              this.doFormItemValidate(fieldName);
            }}
          >
            {unitRender && <template slot="append">{<div style={disabled && { pointerEvents: 'none' }}>{unitRender()}</div>}</template>}
          </el-input>
          {this.createFormItemDesc(descOptions)}
        </el-form-item>
      );
    },
    INPUT_NUMBER(option) {
      const { form } = this;
      const {
        label,
        fieldName,
        labelWidth,
        labelOptions,
        descOptions,
        style = {},
        placeholder = '请输入...',
        disabled,
        isControls = !0,
        min = 0,
        max = 999999999999,
        maxlength = 50,
        step = 1,
        precision,
        change = () => {},
        onEnter = () => {}
      } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-input-number
            v-model={form[fieldName]}
            placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            controls={isControls}
            controls-position="right"
            min={min}
            max={max}
            step={step}
            precision={precision}
            controls={false}
            clearable
            onChange={val => {
              if (maxlength > 0 && typeof val !== 'undefined') {
                const res = Number.parseInt(val).toString();
                if (res.length > maxlength) {
                  form[fieldName] = Number(res.slice(0, maxlength));
                }
              }
              change(form[fieldName]);
            }}
            nativeOnKeydown={e => {
              if (e.keyCode !== 13) return;
              onEnter(e.target.value);
              this.doFormItemValidate(fieldName);
            }}
          />
          {this.createFormItemDesc(descOptions)}
        </el-form-item>
      );
    },
    RANGE_INPUT(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, readonly, disabled, change = () => {} } = option;
      const [startFieldName, endFieldName] = fieldName.split('|');
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-input
            v-model={form[fieldName][0]}
            readonly={readonly}
            disabled={disabled}
            style={{ width: `calc(50% - 7px)` }}
            clearable
            onChange={() => change({ [startFieldName]: form[fieldName][0] })}
          />
          <span style="display: inline-block; text-align: center; width: 14px;">-</span>
          <el-input
            v-model={form[fieldName][1]}
            readonly={readonly}
            disabled={disabled}
            style={{ width: `calc(50% - 7px)` }}
            clearable
            onChange={() => change({ [endFieldName]: form[fieldName][1] })}
          />
        </el-form-item>
      );
    },
    RANGE_INPUT_NUMBER(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, min = 0, max = 999999999999, step = 1, precision, readonly, disabled, change = () => {} } = option;
      const [startVal = min, endVal = max] = form[fieldName];
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-input-number
            v-model={form[fieldName][0]}
            controls-position="right"
            min={min}
            max={endVal}
            step={step}
            precision={precision}
            readonly={readonly}
            disabled={disabled}
            controls={false}
            style={{ width: `calc(50% - 7px)` }}
            clearable
            onChange={() => change(form[fieldName])}
          />
          <span style="display: inline-block; text-align: center; width: 14px;">-</span>
          <el-input-number
            v-model={form[fieldName][1]}
            controls-position="right"
            min={startVal}
            max={max}
            step={step}
            precision={precision}
            readonly={readonly}
            disabled={disabled}
            controls={false}
            style={{ width: `calc(50% - 7px)` }}
            clearable
            onChange={() => change(form[fieldName])}
          />
        </el-form-item>
      );
    },
    INPUT_TREE(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, itemList, style = {}, placeholder = '请输入...', readonly, disabled, defaultExpandAll, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-popover
            v-model={this.visible[fieldName]}
            popper-class="input-tree"
            transition="el-zoom-in-top"
            placement="bottom-start"
            trigger="click"
            on-after-leave={() => {
              this[`${fieldName}TreeFilterTexts`] = '';
              this.treeFilterTextHandle(fieldName);
            }}
          >
            <div class="el-input--small input-tree-wrap" style={{ maxHeight: '250px', overflowY: 'auto', ...style }}>
              <input
                value={this[`${fieldName}TreeFilterTexts`]}
                class="el-input__inner"
                placeholder="树节点过滤"
                onInput={ev => {
                  this[`${fieldName}TreeFilterTexts`] = ev.target.value;
                  this.treeFilterTextHandle(fieldName);
                }}
              />
              <el-tree
                ref={`tree-${fieldName}`}
                style={{ marginTop: '4px' }}
                data={itemList}
                props={this.treeProps}
                defaultExpandAll={!defaultExpandAll}
                expandOnClickNode={false}
                filterNodeMethod={this.filterNodeHandle}
                on-node-click={data => {
                  this.treeNodeClickHandle(fieldName, data);
                  change(this.form[fieldName]);
                }}
              />
            </div>
            <el-input
              slot="reference"
              value={this.createInputTreeValue(fieldName, itemList)}
              placeholder={!disabled ? placeholder : ''}
              readonly={readonly}
              disabled={disabled}
              clearable
              style={disabled && { pointerEvents: 'none' }}
              onClear={() => {
                this.treeNodeClickHandle(fieldName, {});
                change(this.form[fieldName]);
              }}
            />
          </el-popover>
        </el-form-item>
      );
    },
    INPUT_CASCADER(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, itemList = [], options = {}, style = {}, placeholder = '请选择...', readonly, disabled, change = () => {} } = option;
      const { titles = [] } = options;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-popover v-model={this.visible[fieldName]} transition="el-zoom-in-top" placement="bottom-start" trigger="click">
            <div style={{ maxHeight: '250px', overflowY: 'auto', ...style }}>
              <Cascader
                value={form[fieldName]}
                onInput={val => {
                  this.cascaderChangeHandle(fieldName, val);
                }}
                list={itemList}
                labels={titles}
                style={style}
                onChange={data => {
                  change(form[fieldName], this[`${fieldName}CascaderTexts`]);
                }}
                onClose={() => (this.visible[fieldName] = false)}
              />
            </div>
            <el-input
              slot="reference"
              value={this[`${fieldName}CascaderTexts`]}
              placeholder={!disabled ? placeholder : ''}
              readonly={readonly}
              disabled={disabled}
              clearable
              style={disabled && { pointerEvents: 'none' }}
              onClear={() => {
                this.cascaderChangeHandle(fieldName, []);
                change(form[fieldName], this[`${fieldName}CascaderTexts`]);
              }}
            />
          </el-popover>
        </el-form-item>
      );
    },
    SEARCH_HELPER(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, request = {}, style = {}, placeholder = '请输入...', disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-autocomplete
            v-model={form[fieldName]}
            placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            clearable
            onChange={change}
            onSelect={request.change}
            fetchSuggestions={(queryString, cb) => this.querySearchAsync(request, fieldName, queryString, cb)}
          />
        </el-form-item>
      );
    },
    SEARCH_HELPER_WEB(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, itemList, labelOptions, style = {}, placeholder = '请输入...', disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-autocomplete
            v-model={form[fieldName]}
            valueKey="text"
            placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            clearable
            onChange={change}
            fetchSuggestions={(queryString, cb) => this.querySearchHandle(fieldName, queryString, cb)}
            scopedSlots={{
              default: props => {
                const { item } = props;
                return <span>{item.text}</span>;
              }
            }}
          />
        </el-form-item>
      );
    },
    SELECT(option) {
      return this.createSelectHandle(option);
    },
    MULTIPLE_SELECT(option) {
      return this.createSelectHandle(option, true);
    },
    DATE(option) {
      const { form } = this;
      const conf = {
        date: {
          // placeholder: '选择日期',
          valueFormat: 'yyyy-MM-dd HH:mm:ss'
        },
        datetime: {
          // placeholder: '选择时间',
          valueFormat: 'yyyy-MM-dd HH:mm:ss'
        },
        exactdate: {
          // placeholder: '选择日期',
          valueFormat: 'yyyy-MM-dd'
        },
        month: {
          // placeholder: '选择月份',
          valueFormat: 'yyyy-MM'
        },
        year: {
          // placeholder: '选择年份',
          valueFormat: 'yyyy'
        }
      };

      const { label, fieldName, labelWidth, labelOptions, dateType = 'date', defaultTime, minDateTime, maxDateTime, format, valueFormat, style = {}, disabled, change = () => {} } = option;
      let currentVal;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-date-picker
            ref={fieldName}
            type={dateType.replace('exact', '')}
            value={this.formatDate(form[fieldName], valueFormat || conf[dateType].valueFormat)}
            onInput={val => {
              val = val === null ? undefined : val;
              form[fieldName] = val;
            }}
            default-time={defaultTime}
            value-format={valueFormat || conf[dateType].valueFormat}
            format={format || conf[dateType].valueFormat}
            // placeholder={conf[dateType].placeholder}
            disabled={disabled}
            style={{ ...style }}
            picker-options={{
              disabledDate: time => {
                return this.setDisabledDate(time, [minDateTime, maxDateTime]);
              }
            }}
            nativeOnInput={ev => {
              const val = ev.target.value.replace(/(\d{4})-?(\d{2})-?(\d{2})/, '$1-$2-$3');
              currentVal = val;
              ev.target.value = val;
            }}
            onBlur={val => {
              if (!currentVal) return;
              form[fieldName] = this.dateToText(currentVal);
              change(form[fieldName]);
              currentVal = undefined;
            }}
            nativeOnKeydown={ev => {
              if (ev.keyCode === 13) {
                form[fieldName] = this.dateToText(ev.target.value);
                this.$refs[`${fieldName}`].hidePicker();
              }
            }}
            onChange={() => change(form[fieldName])}
          />
        </el-form-item>
      );
    },
    // DATE_TIME -> 为了兼容旧版控件类型参数
    DATE_TIME(option) {
      return this.DATE({ ...option, dateType: 'datetime' });
    },
    RANGE_DATE(option) {
      const { form } = this;
      const conf = {
        daterange: {
          placeholder: ['开始日期', '结束日期'],
          valueFormat: 'yyyy-MM-dd HH:mm:ss'
        },
        datetimerange: {
          placeholder: ['开始时间', '结束时间'],
          valueFormat: 'yyyy-MM-dd HH:mm:ss'
        },
        exactdaterange: {
          placeholder: ['开始日期', '结束日期'],
          valueFormat: 'yyyy-MM-dd'
        },
        monthrange: {
          placeholder: ['开始月份', '结束月份'],
          valueFormat: 'yyyy-MM'
        }
      };
      const { label, fieldName, labelWidth, labelOptions, dateType = 'daterange', minDateTime, maxDateTime, style = {}, disabled, isAfter, change = () => {} } = option;
      // 日期区间快捷键方法
      const createPicker = (picker, num, key) => {
        let end = new Date();
        let start = new Date();
        isAfter ? (end = moment().add(num, key)) : (start = moment().subtract(num, key));
        picker.$emit('pick', [start, end]);
      };
      // 获取 input 节点的索引
      const getInputIndex = (fieldName, el) => {
        const $inputEls = [...this.$refs[fieldName].$el.querySelectorAll('.el-range-input')];
        return $inputEls.findIndex(x => x === el);
      };
      // 设置日期区间数据的值
      const setDateRangeValue = () => {
        for (let i = 0; i < 2; i++) {
          form[fieldName][i] = this.dateToText(form[fieldName][i]) || '';
        }
        form[fieldName] = [...form[fieldName]];
      };
      const createPickers = (picker, start, end) => {
        form[fieldName] = this.formatDate([start, end], conf[dateType].valueFormat);
        picker.$emit('pick', start);
      };
      const pickers = [
        {
          text: '最近一周',
          onClick(picker) {
            createPicker(picker, 1, 'w');
          }
        },
        {
          text: '最近一个月',
          onClick(picker) {
            createPicker(picker, 1, 'M');
          }
        },
        {
          text: '最近三个月',
          onClick(picker) {
            createPicker(picker, 3, 'M');
          }
        },
        {
          text: '最近六个月',
          onClick(picker) {
            createPicker(picker, 6, 'M');
          }
        },
        {
          text: '当月',
          onClick(picker) {
            createPickers(picker, moment().startOf('month'), moment().endOf('month'));
          }
        },
        {
          text: '当年',
          onClick(picker) {
            createPickers(picker, moment().startOf('year'), moment().endOf('year'));
          }
        }
      ];
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-date-picker
            ref={fieldName}
            type={dateType.replace('exact', '')}
            value={this.formatDate(form[fieldName], conf[dateType].valueFormat)}
            onInput={val => {
              val =
                val === null
                  ? []
                  : [
                      moment(val[0]).format(conf[dateType].valueFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')),
                      moment(val[1]).format(conf[dateType].valueFormat.replace('yyyy', 'YYYY').replace('dd', 'DD'))
                    ];
              form[fieldName] = val;
            }}
            value-format={conf[dateType].valueFormat}
            range-separator="-"
            start-placeholder={conf[dateType].placeholder[0]}
            end-placeholder={conf[dateType].placeholder[1]}
            unlink-panels={true}
            disabled={disabled}
            style={{ ...style }}
            pickerOptions={{
              shortcuts: dateType.includes('date') ? pickers : pickers.slice(1),
              disabledDate: time => {
                return this.setDisabledDate(time, [minDateTime, maxDateTime]);
              }
            }}
            nativeOnInput={ev => {
              const v = getInputIndex(fieldName, ev.target);
              const val = ev.target.value.replace(/(\d{4})-?(\d{2})-?(\d{2})/, '$1-$2-$3');
              form[fieldName][v] = val;
            }}
            onBlur={val => {
              setDateRangeValue();
            }}
            nativeOnKeydown={ev => {
              if (ev.keyCode === 13) {
                setDateRangeValue();
                getInputIndex(fieldName, ev.target) && this.$refs[`${fieldName}`].hidePicker();
              }
            }}
            onChange={() => change(form[fieldName])}
          />
        </el-form-item>
      );
    },
    TIME(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, defaultTime, valueFormat = 'HH:mm:ss', style = {}, placeholder = '选择时间', disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-time-picker
            v-model={form[fieldName]}
            pickerOptions={{
              format: valueFormat
            }}
            default-value={defaultTime}
            value-format={valueFormat}
            // placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            onChange={change}
          />
        </el-form-item>
      );
    },
    RANGE_TIME(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, valueFormat = 'HH:mm:ss', style = {}, disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-time-picker
            isRange={true}
            value={form[fieldName].length ? form[fieldName] : undefined}
            onInput={val => {
              val = val === null ? [] : val;
              form[fieldName] = val;
            }}
            value-format={valueFormat}
            range-separator="-"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            disabled={disabled}
            style={{ ...style }}
            onChange={() => change(form[fieldName])}
          />
        </el-form-item>
      );
    },
    TIME_SELECT(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, defaultTime, valueFormat = 'HH:mm', options = {}, style = {}, placeholder = '选择时间', disabled, change = () => {} } = option;
      const { startTime = '00:00', endTime = '23:45', stepTime = '00:15' } = options;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-time-select
            v-model={form[fieldName]}
            pickerOptions={{
              start: startTime,
              end: endTime,
              step: stepTime
            }}
            default-value={defaultTime}
            value-format={valueFormat}
            // placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            onChange={change}
          />
        </el-form-item>
      );
    },
    RANGE_TIME_SELECT(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, valueFormat = 'HH:mm', options = {}, style = {}, disabled, change = () => {} } = option;
      const { startTime = '00:00', endTime = '23:45', stepTime = '00:15' } = options;
      const stepMinute = moment(stepTime, valueFormat).minute();
      const [startVal, endVal] = form[fieldName];
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-time-select
            value={form[fieldName][0]}
            onInput={val => {
              val = val === null ? undefined : val;
              form[fieldName] = [val, endVal];
            }}
            pickerOptions={{
              start: startTime,
              end: endTime,
              step: stepTime,
              maxTime:
                endVal &&
                moment(endVal, valueFormat)
                  .add(stepMinute, 'minutes')
                  .format(valueFormat)
            }}
            value-format={valueFormat}
            placeholder={'开始时间'}
            disabled={disabled}
            style={{ width: `calc(50% - 7px)` }}
            onChange={() => change(form[fieldName])}
          />
          <span style="display: inline-block; text-align: center; width: 14px;">-</span>
          <el-time-select
            value={form[fieldName][1]}
            onInput={val => {
              val = val === null ? undefined : val;
              form[fieldName] = [startVal, val];
            }}
            pickerOptions={{
              start: startTime,
              end: endTime,
              step: stepTime,
              minTime:
                startVal &&
                moment(startVal, valueFormat)
                  .subtract(stepMinute, 'minutes')
                  .format(valueFormat)
            }}
            value-format={valueFormat}
            placeholder={'结束时间'}
            disabled={disabled}
            style={{ width: `calc(50% - 7px)` }}
            onChange={() => change(form[fieldName])}
          />
        </el-form-item>
      );
    },
    CHECKBOX(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, descOptions, options = {}, style = {}, disabled, change = () => {} } = option;
      const { trueValue = '1', falseValue = '0' } = options;
      form[fieldName] !== trueValue && (form[fieldName] = falseValue);
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-checkbox v-model={form[fieldName]} disabled={disabled} style={{ ...style }} trueLabel={trueValue} falseLabel={falseValue} onChange={change} />
          {this.createFormItemDesc(descOptions)}
        </el-form-item>
      );
    },
    MULTIPLE_CHECKBOX(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, descOptions, itemList, limit, style = {}, disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-checkbox-group v-model={form[fieldName]} max={limit} style={{ ...style }} onChange={change}>
            {itemList.map(x => {
              return (
                <el-checkbox key={x.value} label={x.value} disabled={disabled}>
                  {x.render ? x.render(x) : x.text}
                </el-checkbox>
              );
            })}
          </el-checkbox-group>
          {this.createFormItemDesc(descOptions)}
        </el-form-item>
      );
    },
    RADIO(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, descOptions, itemList, style = {}, disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-radio-group v-model={form[fieldName]} style={{ ...style }} onChange={change}>
            {itemList.map(x => (
              <el-radio key={x.value} label={x.value} disabled={disabled}>
                {x.text}
              </el-radio>
            ))}
          </el-radio-group>
          {this.createFormItemDesc(descOptions)}
        </el-form-item>
      );
    },
    TEXT_AREA(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, style = {}, placeholder = '请输入...', disabled, rows = 2, maxlength = 200, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-input
            type="textarea"
            v-model={form[fieldName]}
            placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            clearable
            autosize={{ minRows: rows }}
            maxlength={maxlength}
            showWordLimit
            onChange={change}
          />
        </el-form-item>
      );
    },
    UPLOAD_IMG(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, upload = {}, style = {}, disabled, isCompress, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} ref={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <UploadCropper
            actionUrl={upload.actionUrl}
            initialValue={form[fieldName]}
            style={{ ...style }}
            fixedSize={upload.fixedSize}
            isCalcHeight={upload.isCalcHeight}
            limit={upload.limit || 1}
            params={upload.params}
            titles={upload.titles}
            fixed={upload.fixed}
            outputSize={upload.outputSize}
            tipText={upload.tipText}
            isFileName={upload.isFileName}
            disabled={disabled}
            isCompress={isCompress}
            onSuccess={val => {
              this.fileChangeHandle(fieldName, val);
              change(val);
            }}
          />
        </el-form-item>
      );
    },
    UPLOAD_ALIOSS(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, upload = {}, style = {}, initialValue = [], disabled, change = () => {}, error = () => {} } = option;
      form[fieldName] = form[fieldName] || initialValue;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <ossUpload
            value={form[fieldName]}
            init={upload}
            onGetUrl={val => {
              this.form[fieldName] = val;
              change(val);
            }}
          />
        </el-form-item>
      );
    },
    UPLOAD_FILE(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, StrongDisabled, upload = {}, style = {}, autoUpload, autoUploadDisplay, beforeRemove = () => {}, change = () => {} } = option;
      const disabled = typeof StrongDisabled == 'boolean' ? StrongDisabled : option.disabled;
      if (!Array.isArray(form[fieldName])) return;
      console.log('upload', upload);
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <UploadFile
            actionUrl={upload.actionUrl}
            fileList={form[fieldName]}
            initialValue={form[fieldName]}
            fileTypes={upload.fileTypes}
            fileSize={upload.fileSize}
            fileRatio={upload.fileRatio}
            isOnlyButton={upload.isOnlyButton}
            limit={upload.limit || 1}
            multiple={!!upload.multiple}
            disabled={disabled}
            style={{ ...style }}
            autoUpload={autoUpload}
            autoUploadDisplay={autoUploadDisplay}
            beforeRemove={beforeRemove}
            params={upload.data}
            customFileName={upload.customFileName}
            extra={upload.extra}
            onChange={val => {
              this.fileChangeHandle(fieldName, val);
              change(val);
            }}
          >
            文件上传
          </UploadFile>
        </el-form-item>
      );
    },
    TINYMCE(option) {
      const { form } = this;
      const { label, fieldName, labelWidth, labelOptions, height, upload = {}, disabled, change = () => {} } = option;
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <Tinymce v-model={form[fieldName]} actionUrl={upload.actionUrl} height={height} fixedSize={upload.fixedSize} disabled={disabled} onChange={change} />
        </el-form-item>
      );
    },
    BREAK_SPACE(option) {
      const { label = '标题', style = {}, colRender } = option;
      return colRender ? colRender(option) : <BreakSpace label={label} id={label} labelStyle={style} />;
    },
    createSelectHandle(option, multiple = false) {
      const { form } = this;
      const {
        label,
        fieldName,
        labelWidth,
        labelOptions,
        descOptions,
        filterable,
        request = {},
        style = {},
        placeholder = '请选择...',
        disabled,
        limit = 0,
        clearable = !0,
        change = () => {},
        collapseTags = true
      } = option;
      const { fetchApi, params = {} } = request;
      let { itemList } = option;
      if (!option.itemList && fetchApi) {
        itemList = this[`${fieldName}ItemList`] || [];
        if (!_.isEqual(this[`${fieldName}PrevParams`], params)) {
          this[`${fieldName}PrevParams`] = params;
          this.querySelectOptions(request, fieldName);
        }
      }
      return (
        <el-form-item key={fieldName} label={label} labelWidth={labelWidth} prop={fieldName}>
          {labelOptions && <span slot="label">{this.createFormItemLabel(labelOptions)}</span>}
          <el-select
            multiple={multiple}
            multipleLimit={limit}
            collapseTags={collapseTags}
            filterable={filterable}
            value={form[fieldName]}
            onInput={val => {
              if (!(multiple && filterable)) {
                form[fieldName] = val;
              } else {
                setTimeout(() => (form[fieldName] = val), 20);
              }
            }}
            placeholder={!disabled ? placeholder : ''}
            disabled={disabled}
            style={{ ...style }}
            clearable={clearable}
            on-visible-change={visible => {
              if (filterable && !visible) {
                setTimeout(() => {
                  this.filterMethodHandle(fieldName);
                }, 300);
              }
            }}
            onChange={val => {
              const { text } = itemList.find(x => x.value === val) || {};
              change(val, !multiple ? text : undefined);
              if (!filterable) return;
              this.filterMethodHandle(fieldName, '');
            }}
            filterMethod={queryString => {
              if (!filterable) return;
              this.filterMethodHandle(fieldName, queryString);
            }}
          >
            {itemList.map(x => (
              <el-option key={x.value} label={x.text} value={x.value} title={x.text} disabled={x.disabled} />
            ))}
          </el-select>
          {this.createFormItemDesc(descOptions)}
        </el-form-item>
      );
    },
    // 设置日期控件的禁用状态
    setDisabledDate(time, [minDateTime, maxDateTime]) {
      const min = minDateTime
        ? moment(minDateTime)
            .toDate()
            .getTime()
        : 0;
      const max = maxDateTime
        ? moment(maxDateTime)
            .toDate()
            .getTime()
        : 0;
      if (min && max) {
        return !(time.getTime() >= min && time.getTime() <= max);
      }
      if (!!min) {
        return time.getTime() < min;
      }
      if (!!max) {
        return time.getTime() > max;
      }
      return false;
    },
    // 下拉框的筛选方法
    filterMethodHandle(fieldName, queryString = '') {
      const target = this.formItemList.find(x => x.fieldName === fieldName);
      const itemList = target.itemList || this[`${fieldName}ItemList`] || [];
      if (!this[`${fieldName}OriginItemList`]) {
        this[`${fieldName}OriginItemList`] = itemList;
      }
      const res = queryString ? this[`${fieldName}OriginItemList`].filter(this.createSearchHelpFilter(queryString)) : this[`${fieldName}OriginItemList`];
      if (!this[`${fieldName}ItemList`]) {
        target.itemList = res;
      } else {
        this[`${fieldName}ItemList`] = res;
        this.$forceUpdate();
      }
    },
    // 获取下拉框数据
    async querySelectOptions({ fetchApi, params = {}, datakey = '', valueKey = 'value', textKey = 'text' }, fieldName) {
      if (process.env.MOCK_DATA === 'true') {
        // const res = require('@/mock/sHelperData').default;
        // this[`${fieldName}ItemList`] = res.data.map(x => ({ value: x[valueKey], text: x[textKey] }));
      } else {
        const res = await fetchApi(params);
        if (res.resultCode === 200) {
          const dataList = !datakey ? res.data : _.get(res.data, datakey, []);
          this[`${fieldName}ItemList`] = dataList.map(x => ({ value: x[valueKey], text: x[textKey] }));
        }
      }
      this.$forceUpdate();
    },
    // 获取搜索帮助数据
    async querySearchAsync(request, fieldName, queryString = '', cb) {
      const { fetchApi, params = {}, datakey = '', textKey, valueKey } = request;
      console.log(textKey, valueKey);
      if (process.env.MOCK_DATA === 'true') {
        // const res = require('@/mock/sHelperData').default;
        // setTimeout(() => {
        //   cb(this.createSerachHelperList(res.data, valueKey));
        // }, 500);
      } else {
        const res = await fetchApi({ ...{ [fieldName]: queryString }, ...params });
        if (res.resultCode === 200) {
          const dataList = !datakey ? res.data : _.get(res.data, datakey, []);
          cb(this.createSerachHelperList(dataList, valueKey, textKey));
        }
      }
    },
    // 创建搜索帮助数据列表
    createSerachHelperList(list, valueKey, textKey) {
      return list.map(x => ({ value: `${x[textKey]}`, text: x[valueKey] }));
    },
    querySearchHandle(fieldName, queryString = '', cb) {
      const { itemList = [] } = this.formItemList.find(x => x.fieldName === fieldName) || {};
      const res = queryString ? itemList.filter(this.createSearchHelpFilter(queryString)) : itemList;
      cb(res);
    },
    createSearchHelpFilter(queryString) {
      return state => {
        const pyt = _.flatten(pinyin(state.text, { style: STYLE_FIRST_LETTER })).join('');
        const str = `${state.text}|${pyt}`;
        return str.toLowerCase().includes(queryString.toLowerCase());
      };
    },
    // 创建树节点的值
    createInputTreeValue(fieldName, itemList) {
      let { text = '' } = this.deepFind(itemList, this.form[fieldName]) || {};
      return text;
    },
    // 树控件顶部文本帅选方法
    treeFilterTextHandle(key) {
      this.$refs[`tree-${key}`].filter(this[`${key}TreeFilterTexts`]);
    },
    // 树结构的筛选方法
    filterNodeHandle(value, data) {
      if (!value) return true;
      return data.text.indexOf(value) !== -1;
    },
    // 树节点单机事件
    treeNodeClickHandle(fieldName, { value, disabled }) {
      if (disabled) return;
      this.form[fieldName] = value;
      this.visible[fieldName] = false;
    },
    // 级联选择器值变化处理方法
    cascaderChangeHandle(fieldName, data) {
      this.form[fieldName] = data.map(x => x.value).join(',') || undefined;
      this[`${fieldName}CascaderTexts`] = data.map(x => x.text).join('/');
      // 强制重新渲染组件
      this.$forceUpdate();
    },
    // 文件上传的 change 事件
    fileChangeHandle(fieldName, val) {
      this.form[fieldName] = val;
      this.doFormItemValidate(fieldName);
    },
    async loadingHandler() {
      this.loading = true;
      await sleep(300);
      this.loading = false;
    },
    createFormItem() {
      return this.list
        .filter(x => !x.hidden)
        .map(item => {
          const VNode = !this[item.type] ? null : item.render ? this.RENDER_FORM_ITEM(item) : this[item.type](item);
          VNode['type'] = item.type;
          VNode['fieldName'] = item.fieldName;
          VNode['cols'] = item.selfCols;
          VNode['offsetLeft'] = item.offsetLeftCols;
          VNode['offsetRight'] = item.offsetRightCols;
          return VNode;
        });
    },
    doClearValidate($compRef) {
      $compRef && $compRef.clearValidate();
    },
    doFormItemValidate(fieldName) {
      this.$refs.form.validateField(fieldName);
    },
    excuteFormData(form) {
      this.formItemList
        .filter(x => ['RANGE_INPUT_NUMBER', 'RANGE_TIME_SELECT'].includes(x.type) || (x.type === 'INPUT' && x.numberFormat))
        .forEach(item => {
          const fieldName = item.fieldName;
          if (form[fieldName].length > 0) {
            if (item.numberFormat) {
              form[fieldName] = form[fieldName].replace(/,\s+/g, '');
            } else {
              // 处理可能出现的风险 bug
              form[fieldName] = Object.assign([], [undefined, undefined], form[fieldName]);
              if (form[fieldName].every(x => _.isUndefined(x))) {
                form[fieldName] = [];
              }
              if (form[fieldName].some(x => _.isUndefined(x))) {
                let val = form[fieldName].find(x => !_.isUndefined(x));
                form[fieldName] = [val, val];
              }
            }
          }
        });
      for (let attr in form) {
        if (attr.includes('|') && Array.isArray(form[attr])) {
          let [start, end] = attr.split('|');
          form[start] = form[attr][0];
          form[end] = form[attr][1];
        }
      }
    },
    // 计算目标元素相对于滚动容器的上边距
    calcOffsetTop(_id) {
      let $target = document.getElementById(`fp-${_id}`);
      let top = $target.offsetTop;
      let $parent = $target.offsetParent;
      while ($parent !== null && $parent !== this.scrollContainer) {
        top += $parent.offsetTop;
        $parent = $parent.offsetParent;
      }
      return top;
    },
    // 锚点定位没有通过校验的表单项
    createAnchorFixed(ids) {
      const res = [];
      for (let key in ids) {
        res.push({ fieldName: key, disY: this.calcOffsetTop(key) });
      }
      res.sort((a, b) => a.disY - b.disY);
      this.scrollContainer.scrollTop = res[0].disY || 0;
    },
    // 获取表单组件的值
    getFormData() {
      this.excuteFormData(this.form);
      return new Promise((resolve, reject) => {
        this.$refs.form.validate((valid, fieldNames) => {
          if (!valid) {
            reject(fieldNames);
          } else {
            resolve(this.form);
          }
        });
      });
    },
    submitForm(ev) {
      ev && ev.preventDefault();
      let isErr;
      this.$refs.form.validate(valid => {
        isErr = !valid;
      });
      if (!isErr) {
        this.excuteFormData(this.form);
        this.loadingHandler();
        this.$emit('formChange', this.form);
      }
      return isErr;
    },
    resetForm() {
      let cloneForm = _.cloneDeep(this.form);
      this.$refs.form.resetFields();
      this.formItemList.forEach(x => {
        if (x.noResetable) {
          this.form[x.fieldName] = cloneForm[x.fieldName];
        }
      });
      this.excuteFormData(this.form);
      // 清空变量，释放内存
      cloneForm = null;
      // 解决 附件/图片 重复校验的 bug
      this.$nextTick(() => {
        this.formItemList.forEach(x => {
          if (['UPLOAD_FILE', 'UPLOAD_IMG'].includes(x.type)) {
            this.doClearValidate(this.$refs[x.fieldName]);
          }
        });
      });
    },
    createFormLayout() {
      const unfixTypes = ['MULTIPLE_CHECKBOX', 'TEXT_AREA', 'TINYMCE', 'UPLOAD_IMG', 'UPLOAD_FILE'];
      const colSpan = 24 / this.cols;
      const formItems = this.createFormItem().filter(item => item !== null);
      const colFormItems = formItems.map((Node, i) => {
        const spans = _.isUndefined(Node.cols) ? colSpan : Node.cols * colSpan;
        const offsetLeft = _.isUndefined(Node.offsetLeft) ? 0 : Node.offsetLeft * colSpan;
        const offsetRight = _.isUndefined(Node.offsetRight) ? 0 : this.toPercent(Node.offsetRight / this.cols);
        return (
          <el-col
            key={i}
            type={unfixTypes.includes(Node.type) ? 'UN_FIXED' : 'FIXED'}
            id={Node.type !== 'BREAK_SPACE' ? `fp-${Node.fieldName}` : null}
            class={this.labelPosition === 'top' && Node.type === 'BREAK_SPACE' ? 'BREAK_SPACE_TOP' : ''}
            offset={offsetLeft}
            span={Node.type !== 'BREAK_SPACE' ? spans : 24}
            style={{ marginRight: offsetRight }}
          >
            {Node}
          </el-col>
        );
      });
      return colFormItems;
    },
    createFormButton() {
      const { loading, disabled } = this;
      const colSpan = 24 / this.cols;
      return this.list.length && this.isSubmitBtn && this.formType !== 'show' ? (
        <el-row gutter={10}>
          <el-col key="-" span={colSpan}>
            <el-form-item label={''}>
              <el-button size="small" type="primary" loading={loading} disabled={disabled} onClick={this.submitForm}>
                {this.formType === 'add' ? '保 存' : '修 改'}
              </el-button>
              <el-button size="small" disabled={disabled} onClick={this.resetForm}>
                重 置
              </el-button>
            </el-form-item>
          </el-col>
        </el-row>
      ) : null;
    },
    dateToText(input = '') {
      const res = moment(input, 'YYYYMMDD').format('YYYY-MM-DD');
      return res !== 'Invalid date' ? res : '';
    },
    // 日期格式化
    formatDate(val, vf) {
      const arr = Array.isArray(val) ? val : [val];
      const res = arr.map(x => {
        return !x ? x : moment(x).format(vf.replace('yyyy', 'YYYY').replace('dd', 'DD'));
      });
      return Array.isArray(val) ? res : res[0];
    },
    // 数字格式化
    formatNumber(value = '') {
      value += '';
      if (value.slice(-1) === '.') return value;
      value = value.replace(/,\s+/g, '');
      const list = value.split('.');
      const prefix = list[0].charAt(0) === '-' ? '-' : '';
      let num = prefix ? list[0].slice(1) : list[0];
      let result = '';
      while (num.length > 3) {
        result = `, ${num.slice(-3)}${result}`;
        num = num.slice(0, num.length - 3);
      }
      if (num) {
        result = num + result;
      }
      return `${prefix}${result}${list[1] ? `.${list[1]}` : ''}`;
    },
    // 保密字段格式化方法
    secretFormat(value = '', type) {
      value += '';
      if (type === 'name') {
        value = value.replace(/^([\u4e00-\u9fa5]{1}).+$/, '$1**');
      }
      if (type === 'phone') {
        value = value.replace(/^(\d{3}).+(\d{4})$/, '$1****$2');
      }
      if (type === 'IDnumber') {
        value = value.replace(/^(\d{3}).+(\w{4})$/, '$1***********$2');
      }
      return value;
    },
    // 函数防抖
    debounce(fn, delay) {
      return function(...args) {
        fn.timer && clearTimeout(fn.timer);
        fn.timer = setTimeout(() => fn.apply(this, args), delay);
      };
    },
    // 转百分比
    toPercent(num) {
      return Number(num * 100).toFixed(5) + '%';
    },
    difference(newVal, oldVal) {
      const res = {};
      for (let key in newVal) {
        if (!_.isEqual(newVal[key], oldVal[key])) {
          res[key] = newVal[key];
        }
      }
      return res;
    },
    deepFind(arr, mark) {
      let res = null;
      for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i].children)) {
          res = this.deepFind(arr[i].children, mark);
        }
        if (res !== null) {
          return res;
        }
        if (arr[i].value === mark) {
          res = arr[i];
          break;
        }
      }
      return res;
    },
    // 外部通过组件实例调用的方法
    SUBMIT_FORM() {
      const err = this.submitForm();
      return !err ? this.form : null;
    },
    RESET_FORM() {
      this.resetForm();
    },
    async GET_FORM_DATA() {
      try {
        const res = await this.getFormData();
        return [false, res];
      } catch (err) {
        if (_.isElement(this.scrollContainer)) {
          this.createAnchorFixed(err);
        }
        return [err, null];
      }
    },
    CLEAR_VALIDATE() {
      this.doClearValidate(this.$refs.form);
    }
  },
  render() {
    const { form, rules, labelWidth, labelPosition } = this;
    const cls = [
      `form-panel`,
      {
        ['form-panel-top ']: this.labelPosition === 'top'
      },
      {
        [`form-show`]: this.formType === 'show'
      }
    ];
    return (
      <div class={cls}>
        <el-form ref="form" size="small" model={form} rules={rules} label-width={`${labelWidth}px`} nativeOnSubmit={ev => ev.preventDefault()}>
          <el-row gutter={10}>{this.createFormLayout()}</el-row>
          {this.createFormButton()}
        </el-form>
      </div>
    );
  }
};
</script>

<style lang="less">
.form-panel {
  .el-input {
    input[type='password']::-ms-clear {
      display: none;
    }
    input[type='password']::-ms-reveal {
      display: none;
    }
  }
  .is-disabled {
    textarea[disabled='disabled'],
    input[disabled='disabled'] {
      background-color: #f2f4f8 !important;
    }
  }
  .el-col {
    min-height: 32px;
    margin-bottom: 12px;
    &[type='FIXED'] {
      height: 32px !important;
    }
    .el-form-item {
      margin-bottom: 0;
      .el-form-item__label {
        height: 32px;
        font-size: @textSizeSecondary;
        padding-right: @modulePadding;
        .label-wrap {
          display: inline-block;
          max-width: calc(100% - 10px);
          .el-input__inner {
            border-color: @borderColor;
            padding: 0 8px;
            & + span.el-input__suffix {
              right: 0;
            }
          }
        }
      }
      .el-form-item__content {
        .el-form-item__error {
          margin-top: -2px;
          transform-origin: 0 50%;
          -webkit-transform: scale(0.9);
          transform: scale(0.9);
        }
      }
      .el-select {
        width: 100%;
        .el-select__tags {
          height: 24px;
          overflow-y: auto;
          .el-select__input {
            height: inherit;
          }
        }
      }
      .el-autocomplete {
        width: 100%;
      }
      .el-date-editor {
        width: 100%;
      }
      .el-textarea {
        display: block;
        .el-textarea__inner {
          font-family: inherit;
          overflow-y: auto;
        }
        .el-input__count {
          line-height: 1;
          right: 6px;
        }
      }
      .el-input-number {
        width: 100%;
        .el-input__inner {
          text-align: left !important;
        }
        .el-input-number__increase:hover ~ .el-input .el-input__inner:not(.is-disabled),
        .el-input-number__decrease:hover ~ .el-input .el-input__inner:not(.is-disabled) {
          border-color: @borderColor;
        }
      }
      .el-range-editor {
        padding-right: 5px;
        .el-range-separator {
          padding-left: 0;
          padding-right: 0;
        }
        .el-range__close-icon {
          width: 20px;
        }
      }
      .desc-icon {
        padding: 6px;
        font-size: 18px;
        vertical-align: middle;
      }
      .desc-text {
        font-size: @textSizeSecondary;
      }
      &.is-required .el-form-item__label {
        color: #f5222d;
      }
    }
  }
  &.form-show {
    .is-disabled {
      .el-input__inner,
      .el-textarea__inner {
        border-style: dashed;
        background-color: #fff;
      }
    }
    .el-range-editor.is-disabled {
      border-style: dashed;
      background-color: #fff;
      .el-range-input {
        background-color: #fff;
      }
    }
  }
}
.input-tree {
  .input-tree-wrap {
    padding-right: 10px;
    margin-right: -10px;
  }
  .el-tree {
    .el-tree-node[aria-disabled='true'] > .el-tree-node__content {
      color: @disabledColor;
      background: none;
      cursor: not-allowed;
      .is-leaf {
        pointer-events: none;
      }
    }
  }
}
.el-select-dropdown__item {
  max-width: 500px;
}
.form-panel-top {
  .el-col[type='FIXED'] {
    height: 64px !important;
  }
  .BREAK_SPACE_TOP[type='FIXED'] {
    height: 32px !important;
  }
  .el-form-item {
    height: auto !important;
    .el-form-item__label {
      float: none;
      text-align: center;
    }
    .el-form-item__content {
      margin-left: 0 !important;
    }
    .el-form-item__label {
      margin-left: 0px;
      float: none;
    }
  }
}
</style>
